//
// Created by hoyin on 2022/2/15.
//

#include "Matcher.h"

#define CONTOURS_NONE -1

Matcher::Matcher(Mat &target, const Size &srcSize, const Size &targetSize) {
	setTargetImage(target, targetSize);
}

void Matcher::setOriginImage(Mat &src, const Size& size) {
	if (src.size().empty()) return;
	if (src.size() != size && !size.empty()) {
		resize(src, originImage, size);
	} else {
		originImage = src.clone();
	}
}

void Matcher::setTargetImage(Mat &target, const Size& size) {
	if (target.size().empty()) return;
	if (target.size() != size && !size.empty()) {
		resize(target, targetImage, size);
	} else {
		targetImage = target.clone();
	}
}

Rect2d Matcher::templateMatch(double threshold, int method) {
	Mat matchResult, originGray, targetGray;
	Point minLoc, maxLoc, tarLoc;
	double minVal, maxVal;
	Rect2d result(0, 0, 0, 0);
	if (originImage.empty() || targetImage.empty()) {
		printf("unavailable image.");
	} else {
		if (originImage.channels() == 3) {
			cvtColor(originImage, originGray, CV_BGR2GRAY);
		} else {
			originGray = originImage;
		}
		if (targetImage.channels() == 3) {
			cvtColor(targetImage, targetGray, CV_BGR2GRAY);
		} else {
			targetGray = targetImage;
		}
	}
	matchTemplate(originGray, targetGray, matchResult, method);
//	imshow("matchresult", matchResult);
//	waitKey();
//	destroyAllWindows();
	minMaxLoc(matchResult, &minVal, &maxVal, &minLoc, &maxLoc);
	if (TM_SQDIFF == method || TM_SQDIFF_NORMED == method) {
		tarLoc = minLoc;
		if (minVal < threshold) {
			result = Rect2d(tarLoc, Point(tarLoc.x  + targetImage.cols, tarLoc.y + targetImage.rows));
//			printf("matched, response: %f\n", minVal);
		}
	} else {
		tarLoc = maxLoc;
		if (maxVal > threshold) {
			result = Rect2d(tarLoc, Point(tarLoc.x  + targetImage.cols, tarLoc.y + targetImage.rows));
//			printf("matched, response: %f\n", maxVal);
		}
	}
	return result;
}

vector<Point> Matcher::contoursMatch() {
	Mat srcGray, srcBinary, targetGray, targetBinary;
	if (originImage.channels() == 3) {
		cvtColor(originImage, srcGray, COLOR_BGR2GRAY);
	} else {
		srcGray = originImage;
	}
	if (targetImage.channels() == 3) {
		cvtColor(targetImage, targetGray, COLOR_BGR2GRAY);
	} else {
		targetGray = targetImage;
	}
//	如果图像本身是二值的，则二值化就不需要了
	threshold(srcGray, srcBinary, 0, 255, THRESH_OTSU);
	threshold(targetGray, targetBinary, 0, 255, THRESH_OTSU);
	Mat element = getStructuringElement(MORPH_RECT, Size(7, 7));
	morphologyEx(srcBinary, srcBinary, MORPH_CLOSE, element);
	morphologyEx(targetBinary, targetBinary, MORPH_CLOSE, element);

	vector<vector<Point>> srcContours, targetContours;
	findContours(srcBinary, srcContours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
	findContours(targetBinary, targetContours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);

	double a0min = 1;
	int a0minLoc = CONTOURS_NONE;
	for (int i = 0; i < srcContours.size(); ++i) {
		double a0 = matchShapes(srcContours[i], targetContours[0], CV_CONTOURS_MATCH_I1, 0);
//		cout << "contour: " << i << " similarity:" << a0 << endl;
		if (a0 < a0min) {
			a0min = a0;
			a0minLoc = i;
//			Rect2d targetRect = boundingRect(srcContours[i]);
//			rectangle(canvas, targetRect, Scalar(20, 20, 200), 2);
//			return targetRect;
		}
	}
//	cout << "closest: " << a0minLoc << " similarity: " << a0min << endl << endl;
	if (a0minLoc == CONTOURS_NONE) {
		vector<Point> noneRet;
		noneRet.emplace_back(0, 0);
		return noneRet;
	}
	return srcContours[a0minLoc];
}

void Matcher::contoursMatch(RotatedRect &result) {
	vector<Point> contours;
	if (result.size.empty()) {
		clearRect(result);
	}
	if (isGpuAccelerate) {
#if CUDA_SUPPORT
		contours = contoursMatchWithCUDA();
#endif
	} else {
		contours = contoursMatch();
	}
	result = minAreaRect(contours);
}

void Matcher::contoursMatch(Rect2d &result) {
	vector<Point> contours;
	if (result.empty()) {
		clearRect(result);
	}
	if (isGpuAccelerate) {
#if CUDA_SUPPORT
		contours = contoursMatchWithCUDA();
#endif
	} else {
		contours = contoursMatch();
	}
	result = boundingRect(contours);
}

void Matcher::clearRect(Rect2d &rect2D) {
	rect2D.x = 0;
	rect2D.y = 0;
	rect2D.width = 0;
	rect2D.height = 0;
}

void Matcher::clearRect(RotatedRect &rotatedRect) {
	rotatedRect.center = Point2f(0, 0);
	rotatedRect.angle = 0;
	rotatedRect.size = Size(0, 0);
}

Matcher::Matcher() = default;